// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.IO;
using ILLink.Shared;

namespace Mono.Linker.Steps
{
    public class ProcessReferencesStep : BaseStep
    {
        protected override void Process()
        {
            // Walk over all -reference inputs and resolve any that may need to be rooted.

            // For example:
            // -reference dir/Unreferenced.dll --action copy --trim-mode copyused
            //     In this case we need to check whether Unreferenced has the
            //     IsTrimmable attribute, and root it if not.
            // -reference dir/Unreferenced.dll --action copy --trim-mode copyused --action link Unreferenced
            //     The per-assembly action wins over the default --action or --trim-mode,
            //     so we don't need to load the assembly to check for IsTrimmable attribute.
            // -reference dir/Unreferenced.dll --action link --trim-mode link
            //     In this case, we don't need to load the assembly up-front, because it will
            //     not get the copy/save action, regardless of the IsTrimmable attribute.

            // Note that we don't do the same for assemblies which may be resolved from input directories - such
            // assemblies will only be rooted if something loads them.
            foreach (var assemblyPath in GetInputAssemblyPaths())
            {
                var assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);

                // If there's no way that this reference could have the copy/save action,
                // we don't need to load it up-front.
                if (!MaybeIsFullyPreservedAssembly(assemblyName))
                    continue;

                // For the remaining references, we need to resolve them (which looks for IsTrimmable attribute)
                // to determine the action.
                var assembly = Context.TryResolve(assemblyName);
                if (assembly == null)
                {
                    Context.LogError(null, DiagnosticId.ReferenceAssemblyCouldNotBeLoaded, assemblyPath);
                    continue;
                }

                // If the assigned action (now taking into account the IsTrimmable attribute) requires us
                // to root the assembly, do so.
                if (IsFullyPreservedAction(Annotations.GetAction(assembly)))
                    Annotations.Mark(assembly.MainModule, new DependencyInfo(DependencyKind.AssemblyAction, assembly), new MessageOrigin(assembly));
            }
        }

        IEnumerable<string> GetInputAssemblyPaths()
        {
            var assemblies = new HashSet<string>();
            foreach (var referencePath in Context.Resolver.GetReferencePaths())
            {
                var assemblyName = Path.GetFileNameWithoutExtension(referencePath);
                if (assemblies.Add(assemblyName))
                    yield return referencePath;
            }
        }

        public static bool IsFullyPreservedAction(AssemblyAction action)
        {
            return action == AssemblyAction.Copy || action == AssemblyAction.Save;
        }

        bool MaybeIsFullyPreservedAssembly(string assemblyName)
        {
            if (Context.Actions.TryGetValue(assemblyName, out AssemblyAction action))
                return IsFullyPreservedAction(action);

            return IsFullyPreservedAction(Context.DefaultAction) || IsFullyPreservedAction(Context.TrimAction);
        }
    }
}
